extends Node2D
# 本脚本继承自 Node2D，适用于 2D 游戏场景

# 定义游戏状态枚举，便于管理游戏的不同阶段
enum GameState { READY, RUNNING, ENDED }

# 游戏基本参数
const TOTAL_TRIALS := 648           # 总试次数
const TRIALS_PER_QUARTER := 162     # 每季度试次数
const STIMULUS_DURATION := 0.1      # 刺激呈现时间（100ms）
const TRIAL_INTERVAL := 2.0         # 试次间隔（2000ms）
const RESPONSE_WINDOW := 0.6        # 反应窗口期（600ms）
const TARGET_RATIO_FIRST_HALF := 0.225  # 第一半程目标比例
const TARGET_RATIO_SECOND_HALF := 0.775 # 第二半程目标比例

# 分数设置
const SCORE_HIT := 100        # 正确点击目标的得分
const SCORE_MISS := -20       # 遗漏目标的扣分
const SCORE_FALSE_ALARM := -50  # 错误点击非目标的扣分

# 游戏状态变量
var tova_game_state: GameState = GameState.READY
var tova_current_trial := 0
var tova_current_quarter := 0  # 当前季度（0-3）
var tova_score := 0

# 反应时间记录
var first_half_reaction_times: Array[float] = []   # 第一半程反应时间
var second_half_reaction_times: Array[float] = []  # 第二半程反应时间
var all_reaction_times: Array[float] = []          # 所有反应时间

# 性能指标
var first_half_hits := 0           # 第一半程命中数
var first_half_false_alarms := 0   # 第一半程误报数
var second_half_hits := 0          # 第二半程命中数
var second_half_false_alarms := 0  # 第二半程误报数
var first_half_misses := 0         # 第一半程遗漏数
var second_half_misses := 0        # 第二半程遗漏数

# 试次控制
var target_list: Array[bool] = []  # 预生成的试次类型列表
var responded := false             # 当前试次是否已响应
var trial_start_time := 0.0        # 试次开始时间（毫秒）
var is_current_trial_target := false # 当前试次是否为目标

# 图片相关变量
var target_index := 0  # 从图片数组中随机选择目标图片
var target_image
var distractor_images: Array = []

# 通过 onready 获取场景中各节点引用，用于显示颜色块、计时、UI显示等：
#@onready var stimulus: ColorRect = $Stimulus      # 刺激（颜色块）节点
@onready var timer: Timer = $Timer                # 定时器节点（未在代码中详细使用，可用于扩展）
@onready var score_label: Label = $CanvasLayer/Score    # 显示分数的标签
@onready var countdown_label: Label = $CanvasLayer/ScoreLabel/CountdownLabel  # 显示试次进度的标签
@onready var avg_reaction_time_label: Label = $CanvasLayer/ScoreLabel/AvgReactionTimeLabel  # 显示平均反应时间
@onready var reaction_time_variance_label: Label = $CanvasLayer/ScoreLabel/ReactionTimeVarianceLabel  # 显示反应时间方差
@onready var stimulus: Sprite2D = $Stimulus  # 改为Polygon2D节点

@onready var target_image_label: Label = $target_image_label  # 用于显示目标图片的标签

var display_timer: SceneTreeTimer   # 用于控制刺激显示时间的计时器
var interval_timer: SceneTreeTimer  # 用于试次间隔的计时器

var images = [
	preload("res://蘑菇/蘑菇1.png"),  # 替换为实际图片路径
	preload("res://蘑菇/蘑菇5.png"),  # 替换为实际图片路径
	preload("res://蘑菇/蘑菇6.png"),  # 替换为实际图片路径
	preload("res://蘑菇/蘑菇7.png")   # 替换为实际图片路径
]

# 定义全局变量，保存游戏状态和统计数据：
var game_state: GameState = GameState.READY  # 初始状态为 READY
var current_trial := -1        # 当前试次计数器
var total_trials := 200        # 总试次数（本示例设置为25，可根据需要调整）
var score := 0                # 玩家得分
var reaction_times: Array[float] = []   # 记录玩家正确响应的反应时间（用于显示平均值和方差）
var is_target := false        # 标识当前试次是否为目标试次（绿色），在生成目标列表时使用

# ----- ACS 统计相关变量 -----
# 用于每一组试次（block）统计数据，后续计算 ACS 分数
var block_size := 162                      # 每组试次数量，用于 ACS 计算
var current_block_trial := 0              # 当前组内的试次计数
var block_reaction_times : Array[float] = []  # 当前组内记录目标试次的反应时间
var block_target_hits := 0                # 当前组内目标试次中玩家正确点击的次数
var block_omissions := 0                  # 当前组内目标试次中未响应（遗漏）的次数
var block_commissions := 0                # 当前组内干扰试次中误点击的次数
var all_acs_scores: Array[float] = []

# 在游戏状态变量部分添加时间戳记录
var time_stamps = {
	"game_start": 0.0,  # 改为浮点数以支持毫秒
	"quarter_changes": [],
	"target_appearances": [],
	"player_responses": [],
	"game_end": 0.0
}

# 添加一个辅助函数来获取当前时间（精确到毫秒）
func get_current_time_ms() -> float:
	var datetime = Time.get_datetime_dict_from_system()
	var unix_time = Time.get_unix_time_from_system()
	return unix_time + (Time.get_ticks_msec() % 1000) / 1000.0

# 添加一个辅助函数来格式化时间戳（精确到毫秒）
func format_timestamp(timestamp: float) -> String:
	var datetime = Time.get_datetime_dict_from_system()  # 使用系统时间
	var msec = int((timestamp - floor(timestamp)) * 1000)
	return "%d-%02d-%02d %02d:%02d:%02d.%03d" % [
		datetime.year,
		datetime.month,
		datetime.day,
		datetime.hour,
		datetime.minute,
		datetime.second,
		msec
	]

func _process(delta: float):
	# 每一帧都更新颜色块的位置，确保居中
	#stimulus.position = (Vector2(get_viewport().size) - stimulus.size) / 2
	if tova_current_trial % 5 == 0:
		$Control/Path2D/PathFollow2D/蝴蝶.visible = true
	elif tova_current_trial % 5 == 3:
		$Control/Path2D/PathFollow2D/蝴蝶.visible = false
	
	# 更新按钮位置 - 屏幕中间的下2/3处
	if has_node("CanvasLayer/UI/StartButton"):  # 假设你的按钮节点路径是 UI/StartButton
		var button = $CanvasLayer/UI/StartButton
		var viewport_size = get_viewport().size
		# 计算位置：水平居中，垂直方向在2/3处（减去按钮高度的一半使其真正居中）
		button.position = Vector2(
		(viewport_size.x - button.size.x) / 2,
		(viewport_size.y * 3 / 4) - (button.size.y / 2)
		)
	
	# 更新标签位置 - 屏幕右1/4上1/4处
	if has_node("CanvasLayer/ScoreLabel"):  # 假设你的标签节点路径是 UI/MyLabel
		var label = $CanvasLayer/ScoreLabel
		var viewport_size = get_viewport().size
		# 计算位置：水平方向在3/4处（右1/4），垂直方向在1/4处
		label.position = Vector2(
			viewport_size.x * 2 / 3 - label.size.x / 2,
			viewport_size.y * 1/ 2 
		)
	
	

# 定义一个函数用于计算数组方差（用于计算反应时间方差）
func variance(arr: Array) -> float:
	if arr.size() < 2:
		return 0.0
	# 计算数组的平均值
	var mean = arr.reduce(func(a, b): return a + b) / arr.size()
	var sum_sq_diff = 0.0
	# 累加每个数据与均值的平方差
	for v in arr:
		sum_sq_diff += (v - mean) * (v - mean)
	return sum_sq_diff / arr.size()

# _ready() 在节点准备完毕时调用
func _ready():
	await get_tree().process_frame	# 等待一帧，确保 UI 组件已初始化；process_frame 是信号，无需括号
	randomize()	# 初始化随机种子
	$CanvasLayer/TargetMushroomBox.visible = false
	$CanvasLayer/StartButton.visible = true
	stimulus.visible = false
	$CanvasLayer/game_over.visible = false
	$CanvasLayer/ACS.visible = false
	$CanvasLayer/Ave_ACS.visible = false
	$CanvasLayer/暂停菜单.visible = false
	$CanvasLayer/pause.visible = false
	$Control/Path2D/PathFollow2D/蝴蝶.visible = false
	$CanvasLayer/规则面板/播放.play("规则弹出")
	$CanvasLayer/Score.visible=false
	#stimulus.color = NEUTRAL_COLOR	# 初始时将颜色块设置为中性颜色（灰色）
	update_ui()	# 更新 UI 显示（分数、试次等）



# 生成目标列表，按照TOVA测试要求
func generate_target_list():
	target_list.clear()
	print("\n=== TOVA测试目标序列 ===")
	
	# 第一、二季度（刺激不频繁阶段）：每季度36个目标 + 126个非目标
	for quarter in range(2):
		var quarter_list: Array[bool] = []
		# 添加36个目标
		for i in range(36):
			quarter_list.append(true)
		# 添加126个非目标
		for i in range(126):
			quarter_list.append(false)
		# 随机打乱本季度的试次顺序
		quarter_list.shuffle()
		# 添加到总列表
		target_list.append_array(quarter_list)
		
		# 打印本季度的目标序列
		print("\n第 %d 季度序列（低频目标阶段）:" % (quarter + 1))
		print("总试次数：%d，目标数：36，非目标数：126" % TRIALS_PER_QUARTER)
		var trial_str = ""
		for i in range(quarter_list.size()):
			if i > 0 and i % 20 == 0:  # 每20个换一行
				trial_str += "\n"
			trial_str += "1 " if quarter_list[i] else "0 "
		print(trial_str)
	
	# 第三、四季度（刺激频繁阶段）：每季度126个目标 + 36个非目标
	for quarter in range(2):
		var quarter_list: Array[bool] = []
		# 添加126个目标
		for i in range(126):
			quarter_list.append(true)
		# 添加36个非目标
		for i in range(36):
			quarter_list.append(false)
		# 随机打乱本季度的试次顺序
		quarter_list.shuffle()
		# 添加到总列表
		target_list.append_array(quarter_list)
		
		# 打印本季度的目标序列
		print("\n第 %d 季度序列（高频目标阶段）:" % (quarter + 3))
		print("总试次数：%d，目标数：126，非目标数：36" % TRIALS_PER_QUARTER)
		var trial_str = ""
		for i in range(quarter_list.size()):
			if i > 0 and i % 20 == 0:  # 每20个换一行
				trial_str += "\n"
			trial_str += "1 " if quarter_list[i] else "0 "
		print(trial_str)
	
	print("\n=== 目标序列生成完成 ===")
	print("总试次数：%d" % target_list.size())
	verify_target_distribution()  # 验证分布

# 开始游戏，重置各项统计数据，并生成试次目标列表
func start_game():
	time_stamps["game_start"] = get_current_time_ms()
	# 隐藏开始按钮，显示必要的UI
	$CanvasLayer/StartButton.visible = false
	stimulus.visible = true
	$CanvasLayer/TargetMushroomBox.visible = true
	
	# 设置目标图片
	target_index = randi() % images.size()
	target_image = images[target_index]
	$CanvasLayer/TargetMushroomBox/TargetImageRect.texture = target_image
	
	# 设置干扰图片
	distractor_images.clear()
	for i in images.size():
		if i != target_index:
			distractor_images.append(images[i])
	
	# 等待1秒后开始游戏
	await get_tree().create_timer(1.0).timeout
	
	# 重置游戏状态
	_reset_game_state()
	
	# 生成目标列表并开始第一个试次
	generate_target_list()
	start_trial()

# 重置游戏状态和计数器
func _reset_game_state():
	# 重置基本状态
	tova_game_state = GameState.RUNNING
	tova_current_trial = 0
	tova_current_quarter = 0
	tova_score = 0
	
	# 清空统计数据
	first_half_reaction_times.clear()
	second_half_reaction_times.clear()
	all_reaction_times.clear()
	first_half_hits = 0
	first_half_false_alarms = 0
	second_half_hits = 0
	second_half_false_alarms = 0
	first_half_misses = 0
	second_half_misses = 0

# 开始单个试次
func start_trial():
	if tova_current_trial >= TOTAL_TRIALS:
		end_game()
		return
	
	# 记录季度变化
	if tova_current_trial % TRIALS_PER_QUARTER == 0:
		time_stamps["quarter_changes"].append({
			"quarter": tova_current_trial / TRIALS_PER_QUARTER + 1,
			"time": get_current_time_ms()
		})
	
	# 重置试次状态
	responded = false
	is_current_trial_target = target_list[tova_current_trial]
	
	# 记录试次开始时间
	trial_start_time = Time.get_ticks_msec()
	
	# 如果是目标试次，记录目标出现时间
	if is_current_trial_target:
		time_stamps["target_appearances"].append({
			"trial": tova_current_trial + 1,
			"time": get_current_time_ms()
		})
	
	# 播放音效
	$音效.play()
	
	# 显示刺激
	stimulus.modulate.a = 1.0
	if is_current_trial_target:
		stimulus.texture = target_image
	else:
		stimulus.texture = distractor_images[randi() % distractor_images.size()]
	
	# 设置刺激显示时间（100ms）
	display_timer = get_tree().create_timer(STIMULUS_DURATION)
	display_timer.timeout.connect(_on_display_timeout)
	
	update_ui()

# 当显示计时器超时时调用
func _on_display_timeout():
	# 隐藏刺激
	stimulus.modulate.a = 0.0
	
	# 设置反应窗口期计时器
	var response_timer = get_tree().create_timer(RESPONSE_WINDOW)
	response_timer.timeout.connect(_on_response_window_timeout)

# 反应窗口期结束时调用
func _on_response_window_timeout():
	if not responded:
		# 如果是目标试次且未响应，记录为遗漏并扣分
		if is_current_trial_target:
			tova_score = max(0, tova_score + SCORE_MISS)  # 防止分数为负
			print("遗漏目标！扣分：", SCORE_MISS)
			
			if tova_current_trial < TOTAL_TRIALS / 2:
				first_half_misses += 1
				print("第一半程遗漏")
			else:
				second_half_misses += 1
				print("第二半程遗漏")
	
	# 等待剩余时间后开始下一个试次
	var remaining_time = TRIAL_INTERVAL - STIMULUS_DURATION - RESPONSE_WINDOW
	interval_timer = get_tree().create_timer(remaining_time)
	interval_timer.timeout.connect(func():
		tova_current_trial += 1
		start_trial()
	)

# 处理玩家输入（响应）
func _input(event):
	# 只处理按键事件
	if not (event is InputEventKey):
		return
		
	# 只在游戏运行状态下处理输入
	if tova_game_state != GameState.RUNNING:
		return
		
	# 只处理respond动作
	if not event.is_action_pressed("respond"):
		return
		
	# 如果已经响应过，不再处理
	if responded:
		return
		
	# ===================== 测试代码开始 =====================
	# TODO: 测试完成后请注释掉这段代码
	# 这段代码用于快速测试游戏结束界面
	# 在实际游戏中应该删除或注释掉这段代码
	#print("测试：触发游戏结束")
	#end_game()
	#return
	# ===================== 测试代码结束 =====================
		
	# 计算当前时间
	var current_time = Time.get_ticks_msec()
	var elapsed_time = (current_time - trial_start_time) / 1000.0
	
	# 检查是否在反应窗口期内
	if elapsed_time > STIMULUS_DURATION + RESPONSE_WINDOW:
		# 超出反应窗口期，不处理输入
		return
		
	print("响应动作被触发，经过时间：", elapsed_time, "秒")  # 调试信息
	responded = true
	
	# 计算反应时间（从刺激消失开始计算）
	var rt = elapsed_time - STIMULUS_DURATION
	print("反应时间：", rt, "秒")  # 调试信息
	
	if is_current_trial_target:  # 目标试次
		# 正确点击目标，加分
		tova_score += SCORE_HIT
		print("正确点击！得分：", SCORE_HIT)
		
		# 记录反应时间
		all_reaction_times.append(rt)
		if tova_current_trial < TOTAL_TRIALS / 2:
			first_half_reaction_times.append(rt)
			first_half_hits += 1
			print("第一半程命中！")
		else:
			second_half_reaction_times.append(rt)
			second_half_hits += 1
			print("第二半程命中！")
	else:  # 非目标试次（误报）
		# 错误点击非目标，扣分
		tova_score = max(0, tova_score + SCORE_FALSE_ALARM)
		print("错误点击！扣分：", SCORE_FALSE_ALARM)
		
		if tova_current_trial < TOTAL_TRIALS / 2:
			first_half_false_alarms += 1
			print("第一半程误报")
		else:
			second_half_false_alarms += 1
			print("第二半程误报")
	
	# 立即隐藏刺激（如果还没隐藏）
	stimulus.modulate.a = 0.0
	update_ui()

	if responded:
		time_stamps["player_responses"].append({
			"trial": tova_current_trial + 1,
			"time": get_current_time_ms(),
			"is_target": is_current_trial_target
		})

# 计算d'值
func calculate_d_prime(hits: int, false_alarms: int, total_targets: int, total_non_targets: int) -> float:
	var hit_rate = float(hits) / total_targets
	var fa_rate = float(false_alarms) / total_non_targets
	
	# 防止极端值
	hit_rate = clamp(hit_rate, 0.01, 0.99)
	fa_rate = clamp(fa_rate, 0.01, 0.99)
	
	# 计算z分数
	var z_hit = get_z_score(hit_rate)
	var z_fa = get_z_score(fa_rate)
	
	return z_hit - z_fa

# 计算反应时间变异性（标准差）
func calculate_rt_variability(reaction_times: Array) -> float:
	if reaction_times.size() < 2:
		return 0.0
	
	var mean = reaction_times.reduce(func(a, b): return a + b) / reaction_times.size()
	var sum_sq_diff = 0.0
	for rt in reaction_times:
		sum_sq_diff += (rt - mean) * (rt - mean)
	return sqrt(sum_sq_diff / reaction_times.size())

# 游戏结束，显示最终结果
func end_game():
	time_stamps["game_end"] = get_current_time_ms()
	tova_game_state = GameState.ENDED
	
	# 隐藏游戏元素
	stimulus.visible = false
	$CanvasLayer/TargetMushroomBox.visible = false
	$CanvasLayer/pause.visible = false
	$Control/Path2D/PathFollow2D/蝴蝶.visible = false
	$CanvasLayer/Score.visible = false  # 隐藏分数显示
	
	# 计算最终结果
	var final_results = calculate_final_results()
	
	# 显示简化的结果（只显示ACS分数）
	var result_text = """=== TOVA测试完成 ===

ACS分数: %.2f

测试结果即将保存到文件。
""" % [calculate_final_acs_score()]
	
	# 显示结果界面
	$CanvasLayer/game_over.visible = true
	$CanvasLayer/game_over.text = result_text
	$CanvasLayer/ACS.visible = true
	$CanvasLayer/Ave_ACS.visible = true
	
	# 显示重新开始按钮
	$CanvasLayer/StartButton.visible = true
	$CanvasLayer/StartButton.text = "重新开始"
	
	# 延迟1秒后显示保存对话框
	show_save_dialog_delayed(final_results)

# 新增：延迟显示保存对话框的函数
func show_save_dialog_delayed(results: Dictionary):
	# 创建一个延时计时器
	var timer = get_tree().create_timer(1.0)
	# 等待计时器完成
	await timer.timeout
	# 生成并保存详细报告
	save_detailed_results(results)

# 修改保存文件对话框函数
func save_results_to_file(result_text: String):
	print("正在创建保存对话框...")  # 调试信息
	
	# 创建文件对话框
	var file_dialog = FileDialog.new()
	add_child(file_dialog)
	
	# 设置对话框属性
	file_dialog.access = FileDialog.ACCESS_FILESYSTEM
	file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
	
	# 设置默认路径为桌面，带错误处理
	var desktop_dir = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP)
	if desktop_dir.is_empty():
		# 如果获取桌面路径失败，尝试获取用户目录
		desktop_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
		if desktop_dir.is_empty():
			# 如果连用户目录也获取失败，使用项目目录
			desktop_dir = ProjectSettings.get_setting("application/config/project_path_dir")
		print("警告：无法获取桌面路径，使用备用路径：", desktop_dir)
	
	file_dialog.current_dir = desktop_dir
	print("当前保存路径设置为：", desktop_dir)  # 调试信息
	
	# 设置默认文件名（使用时间戳）
	var datetime = Time.get_datetime_dict_from_system()
	var default_filename = "tova_results_%d%02d%02d_%02d%02d%02d.txt" % [
		datetime.year, datetime.month, datetime.day,
		datetime.hour, datetime.minute, datetime.second
	]
	
	# 将默认文件名复制到剪贴板
	DisplayServer.clipboard_set(default_filename)
	
	# 创建提示对话框
	var notification = AcceptDialog.new()
	add_child(notification)
	notification.dialog_text = "默认文件名已复制到剪贴板！\n如果需要，您可以按 Ctrl+V 粘贴。\n\n默认保存位置：桌面"
	notification.title = "提示"
	notification.popup_centered()
	
	# 3秒后自动关闭提示
	var timer = get_tree().create_timer(3.0)
	timer.timeout.connect(func(): notification.queue_free())
	
	# 设置文件类型过滤器
	file_dialog.filters = ["*.txt ; Text Files"]
	
	# 设置对话框标题
	file_dialog.title = "选择保存位置（必须保存）"
	
	# 设置对话框大小
	file_dialog.size = Vector2(250, 150)
	file_dialog.min_size = Vector2(200, 120)
	
	# 设置对话框属性
	file_dialog.unresizable = false  # 允许调整大小
	file_dialog.exclusive = true
	file_dialog.always_on_top = true
	
	# 连接信号
	file_dialog.file_selected.connect(_on_file_selected.bind(result_text))
	file_dialog.canceled.connect(func(): _on_save_canceled(file_dialog, result_text))
	file_dialog.close_requested.connect(func(): _on_save_canceled(file_dialog, result_text))
	
	# 自定义对话框行为
	file_dialog.current_file = default_filename
	
	# 添加自定义处理以保持文件名
	file_dialog.dir_selected.connect(func(dir: String):
		# 当切换目录时，重新设置文件名
		file_dialog.current_file = default_filename
	)
	
	# 显示对话框
	print("准备显示文件对话框...")
	file_dialog.popup_centered_ratio(0.25)
	print("文件对话框已弹出")

# 添加取消保存的处理函数
func _on_save_canceled(dialog: FileDialog, result_text: String):
	# 创建提示对话框
	var alert = AcceptDialog.new()
	add_child(alert)
	alert.dialog_text = "必须保存测试结果才能继续！"
	alert.title = "提示"
	
	# 连接确认按钮信号
	alert.confirmed.connect(func():
		alert.queue_free()
		# 重新显示保存对话框
		dialog.popup_centered_ratio(0.25)
	)
	
	# 连接关闭请求信号
	alert.close_requested.connect(func():
		alert.queue_free()
		# 重新显示保存对话框
		dialog.popup_centered_ratio(0.25)
	)
	
	alert.popup_centered()

# 修改文件选择后的处理函数
func _on_file_selected(path: String, result_text: String):
	var file = FileAccess.open(path, FileAccess.WRITE)
	if file:
		file.store_string(result_text)
		print("\n结果已保存到文件：", path)
		# 显示保存成功消息
		show_save_success_message(path)
		
		# 清理文件对话框
		var dialog = get_node_or_null("FileDialog")
		if dialog:
			dialog.queue_free()
	else:
		print("\n保存失败：无法写入文件")
		# 显示保存失败消息并重新显示保存对话框
		var error_dialog = AcceptDialog.new()
		add_child(error_dialog)
		error_dialog.dialog_text = "保存失败：无法写入文件\n请选择其他位置保存。"
		error_dialog.title = "保存失败"
		
		# 连接确认按钮信号
		error_dialog.confirmed.connect(func():
			error_dialog.queue_free()
			# 重新显示保存对话框
			var dialog = get_node_or_null("FileDialog")
			if dialog:
				dialog.popup_centered_ratio(0.25)
		)
		
		error_dialog.popup_centered()

# 显示保存成功消息
func show_save_success_message(path: String):
	var message_label = Label.new()
	message_label.text = "结果已保存到：\n" + path
	message_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
	message_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
	
	var popup = AcceptDialog.new()
	add_child(popup)
	popup.add_child(message_label)
	popup.title = "保存成功"
	popup.size = Vector2(400, 200)
	
	# 3秒后自动关闭
	popup.popup_centered()
	await get_tree().create_timer(3.0).timeout
	popup.queue_free()

# 更新 UI 信息，包括分数、试次、平均反应时间和反应时间方差
func update_ui():
	trial_start_time = Time.get_ticks_msec()
	score_label.text = "分数: %d" % tova_score
	countdown_label.text = "试次: %d/%d" % [tova_current_trial + 1, TOTAL_TRIALS]
	
	# 计算并显示反应时间统计
	var avg_rt = 0.0
	var rt_var = 0.0
	if all_reaction_times.size() > 0:
		avg_rt = all_reaction_times.reduce(func(a, b): return a + b) / all_reaction_times.size()
		rt_var = calculate_rt_variability(all_reaction_times)
	
	avg_reaction_time_label.text = "平均反应时间: %.3f 秒" % avg_rt
	reaction_time_variance_label.text = "反应时间变异性: %.4f" % rt_var

# 显示游戏最终结果，在控制台打印
func show_results():
	var avg_rt = 0.0
	if reaction_times.size() > 0:
		avg_rt = reaction_times.reduce(func(a, b): return a + b) / reaction_times.size()
	var result_text = """Final Score: %d
	Average Reaction Time: %.2f ms
	Correct Responses: %d/%d
	""" % [tova_score, avg_rt * 1000, reaction_times.size(), 10]
	#print(result_text)

# ----- ACS 分数计算部分 -----
# 计算并显示当前组内 ACS 分数和相关指标
func calculate_and_display_ACS():
	var avg_rt = 0.0
	var rt_std = 0.0
	if block_reaction_times.size() > 0:
		avg_rt = block_reaction_times.reduce(func(a, b): return a + b) / block_reaction_times.size()
		rt_std = calculate_std(block_reaction_times, avg_rt)
	
	# 计算命中率：假设组内目标试次数固定为 5（根据生成目标列表，此处示例为 5）
	var hit_rate = block_target_hits / 10.0
	# 计算误报率：组内干扰试次数 = block_size - 目标试次数；此示例中 block_size 为 50，则干扰数为 45
	var false_alarm_rate = block_commissions / 40.0
	
	# 将概率转换为 Z 分数，用于计算 d'
	var z_hit = get_z_score(hit_rate)
	var z_false_alarm = get_z_score(false_alarm_rate)
	# d' 为误报 Z 分数与命中 Z 分数之差
	var d_prime =  z_hit -z_false_alarm
	
	# 计算反应时间和变异性（RTV）的 Z 分数（需要根据常模数据调整均值和标准差）
	var rt_z = get_rt_z(avg_rt)
	var var_z = get_var_z(rt_std)
	
	# 最终 ACS 分数公式：ACS = RT Z + d' + RTV Z + 1.80
	var acs = rt_z + d_prime + var_z + 1.80
	
	# ------ 在此处保存本 block 的 acs 分数 ------
	all_acs_scores.append(acs)
	
	print("Block ACS:", acs, 
		  "\nAvg RT:", avg_rt, 
		  "\nRT Variability:", rt_std, 
		  "\nOmissions:", block_omissions, 
		  "\nCommissions:", block_commissions)
	
# 计算标准差函数（与上面的 variance 函数类似，此处单独定义用于 ACS 计算）
func calculate_std(arr: Array, mean: float) -> float:
	var sum_sq_diff = 0.0
	for rt in arr:
		sum_sq_diff += (rt - mean) * (rt - mean)
	return sqrt(sum_sq_diff / arr.size())

# 将概率转换为 Z 分数，内部调用逆正态分布函数
func get_z_score(rate: float) -> float:
	rate = clamp(rate, 0.00001, 0.99999)
	return get_tova_z(rate)

# 计算反应时间 Z 分数： (rt - 均值) / 标准差
func get_rt_z(rt: float) -> float:
	var mean_rt = 0.5349  # 示例均值（秒），根据常模数据调整
	var std_rt = 0.0843  # 示例标准差
	return (rt - mean_rt) / std_rt

# 计算反应时间变异性 Z 分数： (rt_std - 均值) / 标准差
func get_var_z(rt_std: float) -> float:
	var mean_std = 0.0791  # 示例均值
	var std_std = 0.0190  # 示例标准差
	return (rt_std - mean_std) / std_std

# 逆正态分布函数（采用 Abramowitz & Stegun 公式的近似实现）
func get_tova_z(rate: float) -> float:
	rate = clamp(rate, 0.00001, 0.99999)
	var p = 1.0 - rate
	var T = sqrt(log(1.0 / (p * p)))
	var c0 = 2.515517
	var c1 = 0.802853
	var c2 = 0.010328
	var d1 = 1.432788
	var d2 = 0.189269
	var d3 = 0.001308
	var numerator = c0 + c1 * T + c2 * T * T
	var denominator = 1.0 + d1 * T + d2 * T * T + d3 * T * T * T
	return T - numerator / denominator


# 处理开始按钮按下事件
func _on_start_button_pressed():
	$CanvasLayer/ACS.visible = false
	$CanvasLayer/Ave_ACS.visible = false
	$CanvasLayer/game_over.visible = false
	$CanvasLayer/pause.visible = true
	$CanvasLayer/规则面板/播放.play("规则收纳")
	$CanvasLayer/Score.visible=true
	start_game()

# 验证目标列表的分布
func verify_target_distribution():
	var quarter_size = TRIALS_PER_QUARTER
	var targets_by_quarter = [0, 0, 0, 0]
	
	# 统计每个季度的目标数量
	for i in range(target_list.size()):
		var quarter = i / quarter_size
		if target_list[i]:
			targets_by_quarter[quarter] += 1
	
	print("\n=== 目标分布验证 ===")
	for i in range(4):
		var target_ratio = float(targets_by_quarter[i]) / quarter_size * 100
		var expected_ratio = 22.5 if i < 2 else 77.5
		var phase_name = "低频目标阶段" if i < 2 else "高频目标阶段"
		print("第 %d 季度（%s）:" % [i + 1, phase_name])
		print("目标数量: %d / %d (%.1f%%), 预期: %.1f%%" % 
			[targets_by_quarter[i], quarter_size, target_ratio, expected_ratio])
	
	print("\n总目标数量: %d / %d" % [targets_by_quarter.reduce(func(a, b): return a + b), TOTAL_TRIALS])
	print("=== 验证完成 ===\n")

# 计算最终ACS分数
func calculate_final_acs_score() -> float:
	if all_acs_scores.size() == 0:
		return 0.0
	
	# 计算所有ACS分数的平均值
	var total_acs = all_acs_scores.reduce(func(a, b): return a + b)
	return total_acs / all_acs_scores.size()

# 计算最终结果
func calculate_final_results() -> Dictionary:
	var results = {}
	
	# 计算第一半程平均反应时间
	results.first_half_avg_rt = 0.0
	if first_half_reaction_times.size() > 0:
		results.first_half_avg_rt = first_half_reaction_times.reduce(func(a, b): return a + b) / first_half_reaction_times.size()
	
	# 计算第二半程d'值
	results.second_half_d_prime = calculate_d_prime(
		second_half_hits,
		second_half_false_alarms,
		TRIALS_PER_QUARTER * 2 * TARGET_RATIO_SECOND_HALF,
		TRIALS_PER_QUARTER * 2 * (1 - TARGET_RATIO_SECOND_HALF)
	)
	
	# 计算整体反应时间变异性
	results.total_rt_variability = calculate_rt_variability(all_reaction_times)
	
	# 计算命中率和误报率
	results.first_half_hit_rate = float(first_half_hits) / (TRIALS_PER_QUARTER * 2 * TARGET_RATIO_FIRST_HALF)
	results.first_half_fa_rate = float(first_half_false_alarms) / (TRIALS_PER_QUARTER * 2 * (1 - TARGET_RATIO_FIRST_HALF))
	results.second_half_hit_rate = float(second_half_hits) / (TRIALS_PER_QUARTER * 2 * TARGET_RATIO_SECOND_HALF)
	results.second_half_fa_rate = float(second_half_false_alarms) / (TRIALS_PER_QUARTER * 2 * (1 - TARGET_RATIO_SECOND_HALF))
	
	return results

# 修改保存详细结果的函数
func save_detailed_results(results: Dictionary):
	var detailed_report = """=== TOVA测试详细报告 ===\n
时间记录：
游戏开始时间：%s
""" % [format_timestamp(time_stamps["game_start"])]

	# 添加季度切换时间
	detailed_report += "\n季度切换时间：\n"
	for quarter in time_stamps["quarter_changes"]:
		detailed_report += "第%d季度开始：%s\n" % [
			quarter["quarter"],
			format_timestamp(quarter["time"])
		]

	# 添加目标出现和玩家响应记录
	detailed_report += "\n目标出现和玩家响应记录：\n"
	for i in range(len(time_stamps["target_appearances"])):
		var target = time_stamps["target_appearances"][i]
		detailed_report += "试次%d - 目标出现时间：%s\n" % [
			target["trial"],
			format_timestamp(target["time"])
		]

	for response in time_stamps["player_responses"]:
		detailed_report += "试次%d - 玩家响应时间：%s（%s）\n" % [
			response["trial"],
			format_timestamp(response["time"]),
			"目标试次" if response["is_target"] else "非目标试次"
		]

	detailed_report += "\n游戏结束时间：%s\n" % [
		format_timestamp(time_stamps["game_end"])
	]

	# 添加测试结果统计
	detailed_report += """
测试结果统计：
总分: %d

第一半程（低频目标阶段）:
- 平均反应时间: %.3f 秒
- 命中率: %.1f%%
- 误报率: %.1f%%
- 遗漏数: %d

第二半程（高频目标阶段）:
- d'值（反应辨别力）: %.3f
- 命中率: %.1f%%
- 误报率: %.1f%%
- 遗漏数: %d

整体表现:
- 反应时间变异性: %.3f
- 总命中数: %d
- 总误报数: %d
- 总遗漏数: %d
""" % [
		tova_score,
		results.first_half_avg_rt,
		results.first_half_hit_rate * 100,
		results.first_half_fa_rate * 100,
		first_half_misses,
		results.second_half_d_prime,
		results.second_half_hit_rate * 100,
		results.second_half_fa_rate * 100,
		second_half_misses,
		results.total_rt_variability,
		first_half_hits + second_half_hits,
		first_half_false_alarms + second_half_false_alarms,
		first_half_misses + second_half_misses
	]

	# 直接调用保存对话框
	save_results_to_file(detailed_report)
